/*
 * Copyright (c) 2006-2010 Sun Microsystems, Inc.
 * Copyright (c) 2010 Oracle
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package de.hsrm;

import com.sun.spot.resources.Resources;
import com.sun.spot.resources.transducers.ISwitch;
import com.sun.spot.resources.transducers.ISwitchListener;
import com.sun.spot.resources.transducers.LEDColor;
import com.sun.spot.resources.transducers.ITriColorLEDArray;
import com.sun.spot.resources.transducers.SwitchEvent;
import com.sun.spot.io.j2me.radiogram.*;

import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.service.BootloaderListenerService;
import com.sun.spot.util.IEEEAddress;
import com.sun.squawk.util.MathUtils;
import java.io.*;
import java.util.Hashtable;
import java.util.Vector;
import javax.microedition.io.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/**
 * The startApp method of this class is called by the VM to start the
 * application.
 *
 * The manifest specifies this class as MIDlet-1, which means it will
 * be selected for execution.
 */
public class CarMidlet extends MIDlet implements ISwitchListener{

    private ITriColorLEDArray leds = (ITriColorLEDArray) Resources.lookup(ITriColorLEDArray.class);
//    private RadiogramConnection tx = null;
//    private Radiogram xdg;
    private int addressDisplay = -1;
    
    private int myAddr = Address.NONE;
    private final int SEQNR_BUFFERSIZE = 100;
    private int[] sentSeqBuffer = new int[SEQNR_BUFFERSIZE];
    private int sentSeqBufferIndex = 0;
    private int lastMsgSeqNr = -2;
    RadiogramConnection tx = null;
    Radiogram xdg;
       
    private ISwitch sw1 = (ISwitch)Resources.lookup(ISwitch.class, "SW1");
    private ISwitch sw2 = (ISwitch)Resources.lookup(ISwitch.class, "SW2");
    
    protected void startApp() throws MIDletStateChangeException {
        BootloaderListenerService.getInstance().start();   // monitor the USB (if connected) and recognize commands from host
        sw1.addISwitchListener(this);
        sw2.addISwitchListener(this);
        long ourAddr = RadioFactory.getRadioPolicyManager().getIEEEAddress();
        System.out.println("Our radio address = " + IEEEAddress.toDottedHex(ourAddr));
        
        leds.setColor(LEDColor.CYAN);
        leds.setOn();
        
        //String myAddress = generateAddress(0,1,1,15);
        //System.out.println("Our protocol address = " + myAddress);
        
        
        try {
            
            tx = (RadiogramConnection)Connector.open("radiogram://broadcast:123");
            xdg = (Radiogram)tx.newDatagram(tx.getMaximumLength());
            
            RadiogramConnection rx = (RadiogramConnection)Connector.open("radiogram://:123");
            Radiogram rdg = (Radiogram)rx.newDatagram(rx.getMaximumLength());
            while (true) {
                try {
                    rx.receive(rdg);
                    evaluateIncomingMessage(rdg);                   
                    
                } catch (IOException ex) {
                    System.out.println("Error receiving packet: " + ex);
                    ex.printStackTrace();
                }
            }
        } catch (IOException ex) {
            System.out.println("Error opening connections: " + ex);
            ex.printStackTrace();
        }                     // cause the MIDlet to exit
    }
    
    protected void pauseApp() {
        // This is not currently called by the Squawk VM
    }
    
    /**
     * Called if the MIDlet is terminated by the system.
     * I.e. if startApp throws any exception other than MIDletStateChangeException,
     * if the isolate running the MIDlet is killed with Isolate.exit(), or
     * if VM.stopVM() is called.
     * 
     * It is not called if MIDlet.notifyDestroyed() was called.
     *
     * @param unconditional If true when this method is called, the MIDlet must
     *    cleanup and release all resources. If false the MIDlet may throw
     *    MIDletStateChangeException  to indicate it does not want to be destroyed
     *    at this time.
     */
    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
        leds.setOff();
        
    }
    
    protected void evaluateIncomingMessage(Radiogram incomingRdg)
    {
        try {
                int sourceAddr = incomingRdg.readInt();
                int destAddr = incomingRdg.readInt();
                int msgType = incomingRdg.readInt();
                int msgSeqNr = incomingRdg.readInt();
                int assignedAddr = incomingRdg.readInt();
                int data = incomingRdg.readInt();                
                
                if(!checkSeqNr(msgSeqNr))
                {
                    if (!checkAddr(destAddr))
                    {
                        if(msgSeqNr == lastMsgSeqNr + 1)
                        {
                            if(msgType == MsgType.SENDADDR)
                            {
                                System.out.println("\nAddress assigned:\n"
                            + "src: " + String.valueOf(sourceAddr) 
                            + "\ndest: " + String.valueOf(destAddr)
                            + "\ntype: " + String.valueOf(msgType)
                            + "\nSeqNr: " + String.valueOf(msgSeqNr)
                            + "\nassigned Address: " + String.valueOf(assignedAddr));
                                myAddr = assignedAddr;
                                lastMsgSeqNr = Sequence.getSeqNr();
                                sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = lastMsgSeqNr;
                                sentSeqBufferIndex++;
                                sendMessage(myAddr, sourceAddr, MsgType.ACK, lastMsgSeqNr, assignedAddr, 0);
                                showStatus(addressToDecimal(myAddr), LEDColor.RED);                               
                            }

                        }                    
                        else 
                        {
//                            System.out.println("\nMessage not for me received and sent forward:\n"
//                            + "src: " + String.valueOf(sourceAddr) 
//                            + "\ndest: " + String.valueOf(destAddr)
//                            + "\ntype: " + String.valueOf(msgType)
//                            + "\nSeqNr: " + String.valueOf(msgSeqNr)
//                            + "\nassigned Address: " + String.valueOf(assignedAddr));
                            sendMessage(sourceAddr, destAddr, msgType, msgSeqNr, assignedAddr, data);
                        }



                    }
                    else
                    {
                        System.out.println("\nCar checked:\n"
                                + "msgType: " + String.valueOf(msgType));
                        switch(msgType)
                        {
                            case MsgType.GETADDR:
                                break;                            
                            case MsgType.ACK:
                                break;
                            case MsgType.MSG:
                                break;
//                            case MsgType.CHECK:
//                                System.out.println("\nCar checked:\n"
//                                + "src: " + String.valueOf(sourceAddr) 
//                                + "\ndest: " + String.valueOf(destAddr)
//                                + "\ntype: " + String.valueOf(msgType)
//                                + "\nSeqNr: " + String.valueOf(msgSeqNr));
//                                lastMsgSeqNr = Sequence.getSeqNr();
//                                sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = lastMsgSeqNr;
//                                sentSeqBufferIndex++;
//                                sendMessage(myAddr, sourceAddr, MsgType.ACK, lastMsgSeqNr, assignedAddr, 0);
//                                break;
                            case MsgType.SIGNAL:
                                System.out.println("\nSignal Received:\n"
                                + "src: " + String.valueOf(sourceAddr) 
                                + "\ndest: " + String.valueOf(destAddr)
                                + "\ntype: " + String.valueOf(msgType)
                                + "\nSeqNr: " + String.valueOf(msgSeqNr)
                                + "\nSignal: " + String.valueOf(data));
                                setSignalColor(data);
                        }

                    }
                }
                
               
            } catch (IOException ex) {
                    System.out.println("Error evaluating packet: " + ex);
                    ex.printStackTrace();
            }
    }
    
    public void switchReleased(SwitchEvent evt) {
        if (evt.getSwitch() == sw1) {
            if (myAddr == Address.NONE)
            {
                lastMsgSeqNr = Sequence.getSeqNr();
                sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = lastMsgSeqNr;
                sentSeqBufferIndex++;
                sendMessage(myAddr, Address.COORDINATOR, MsgType.GETADDR, lastMsgSeqNr , 0, decimalToAddress(addressDisplay));
            }
        }
        else if (myAddr == Address.NONE)
        {
            addressDisplay++;
            if((addressDisplay%4 == 3) && (addressDisplay != 0))
                addressDisplay++;
            addressDisplay = addressDisplay%16;
            showStatus(addressDisplay, LEDColor.WHITE);
        }
        else{
           resetCar();           
        }
    }
    
    private int decimalToAddress(int decimalNumber)
    {
        int result = 0;
        result += (decimalNumber/4) * 10;
        result += (decimalNumber%4);        
        return result;
    }
    
    private int addressToDecimal(int address)
    {
        int result = 0;
        
        address = address%10000;
        
        int location = address/1000;
        int lane = (address/100)%10;
        int car = address%100;
        
        result = location * (int)MathUtils.pow(2., 6.);
        result = result + (lane * (int)MathUtils.pow(2., 4.));
        result = result + car;
        return result;
    }

    public void switchPressed(SwitchEvent evt) {
    }
    
           
    protected void sendMessage(int msgSource, int msgDest, int msgType, int msgSeqNr, int assignedAddr, int data)
    {
        try {
                        
            xdg.reset();
            xdg.writeInt(msgSource);
            xdg.writeInt(msgDest);
            xdg.writeInt(msgType);
            xdg.writeInt(msgSeqNr);
            xdg.writeInt(assignedAddr);
            xdg.writeInt(data);
            tx.send(xdg);
            
            if(msgType == MsgType.GETADDR)
            {    
                System.out.println("\nAddress Request sent:\n"
                        + "src: " + String.valueOf(msgSource) 
                        + "\ndest: " + String.valueOf(msgDest)
                        + "\ntype: " + String.valueOf(msgType)
                        + "\nSeqNr: " + String.valueOf(msgSeqNr)
                        + "\nassigned Address: " + String.valueOf(assignedAddr));
            }
            
            if(msgType == MsgType.ACK)
            {    
                System.out.println("\nAddress Acknowledge sent:\n"
                        + "src: " + String.valueOf(msgSource) 
                        + "\ndest: " + String.valueOf(msgDest)
                        + "\ntype: " + String.valueOf(msgType)
                        + "\nSeqNr: " + String.valueOf(msgSeqNr)
                        + "\nassigned Address: " + String.valueOf(assignedAddr));
            }
            
            } catch (IOException ex){
            System.out.println("Error sending packet: " + ex);
            ex.printStackTrace();
        }
    }
    
    protected boolean checkSeqNr(int SeqNr)
    {
        for (int i = 0; i < SEQNR_BUFFERSIZE; i++)
        {
            if (sentSeqBuffer[i] == SeqNr)
                return true;
        }
        
        sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = SeqNr;
        sentSeqBufferIndex++;
        return false;
    }
    
    protected boolean checkAddr(int address)
    {
        System.out.println("\nAdress Check:\n"
                                + "addr: " + String.valueOf(address) 
                                + "   myAddr: " + String.valueOf(myAddr));
        if (myAddr == Address.NONE)
            return false;
        if (address == myAddr)
            return true;
        if(((myAddr/100) == (address/100)) && (address%100 == 0))
        {
            System.out.println("multicast");
            return true;
        }
        return false;
    }
    
    private void showStatus(int decimalValue, LEDColor color) {
        leds.setColor(color);
        for (int i = 7, bit = 1; i >= 0; i--, bit <<= 1) {
            if ((decimalValue & bit) != 0) {
                leds.getLED(i).setOn();
            } else {
                leds.getLED(i).setOff();
            }
        }
    }
    
    private void setSignalColor(int signal) {
        if(signal == SignalColor.GREEN)
        {
            showStatus (addressDisplay, LEDColor.GREEN);
            new Thread(){
                public void run()
                {
                    try
                    {
                        sleep(Util.GREEN_TIME);
                        showStatus(addressDisplay, LEDColor.RED);
                    } catch (Exception ex) {

                    }

                }
            }.start();
        }
        else if (signal == SignalColor.RED)
        {
            showStatus(addressDisplay, LEDColor.RED);
            System.out.println("\nCar checked:\n");
            lastMsgSeqNr = Sequence.getSeqNr();
            sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = lastMsgSeqNr;
            sentSeqBufferIndex++;
            sendMessage(myAddr, Address.COORDINATOR, MsgType.CHECK, lastMsgSeqNr, 0, 0);
        }
        
    }
    
    private void resetCar()
    {
//         if (myAddr != Address.NONE){
//             lastMsgSeqNr = Sequence.getSeqNr();
//             sentSeqBuffer[sentSeqBufferIndex%SEQNR_BUFFERSIZE] = lastMsgSeqNr;
//             sentSeqBufferIndex++;
//             sendMessage(myAddr, Address.COORDINATOR, MsgType.SIGNAL, lastMsgSeqNr , 0, 0);
//         }
         addressDisplay = 0;
         myAddr = Address.NONE;
         leds.setColor(LEDColor.CYAN);
         leds.setOn();         
    }
    
     
}